# Xây dựng từng khối tokenizer

<DocNotebookDropdown
  classNames="absolute z-10 right-0 top-0"
  options={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/vi/chapter6/section8.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/vi/chapter6/section8.ipynb"},
]} />

Như chúng ta đã thấy trong các phần trước, tokenize bao gồm một số bước:

- Chuẩn hóa (mọi thao tác dọn dẹp văn bản được cho là cần thiết, chẳng hạn như xóa dấu cách hoặc dấu, chuẩn hóa Unicode, v.v.)
- Tiền tokenize (chia nhỏ đầu vào thành các từ)
- Đưa đầu vào thông qua mô hình (sử dụng các từ được tiền tokenize để tạo ra một chuỗi token)
- Hậu xử lý (thêm token đặc biệt của trình tokenize, tạo attention mask và ID token)

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter6/tokenization_pipeline.svg" alt="The tokenization pipeline.">
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter6/tokenization_pipeline-dark.svg" alt="The tokenization pipeline.">
</div>

Thư viện 🤗 Tokenizers đã được xây dựng để cung cấp nhiều sự lựa chọn cho các bước này, và ta có thể kết hợp và nối chúng với nhau. Trong phần này, chúng ta sẽ xem các có thể xây một tokenizer từ đầu, trái ngược với cách huấn luyện một tokenizer mới từ cái cũ như ta đã làm ở [phần 2](/course/chapter6/2). Chúng ta sẽ có thể xây bất kì kiểu tokenizer nào ta có thể nghĩ ra!

<Youtube id="MR8tZm5ViWU"/>

Chính xác hơn, thư viện được xây dựng tập trung vào lớp `Tokenizer` với các khối được tập hợp lại trong các mô-đun con:

- `normalizers` chứa tất cả các kiểu `Normalizer` bạn có thể sử dụng (hoàn thiện danh sách tại [đây](https://huggingface.co/docs/tokenizers/api/normalizers)).
- `pre_tokenizers` chứa tất cả các kiểu `PreTokenizer` bạn có thể sử dụng (hoàn thiện danh sách tại [đây](https://huggingface.co/docs/tokenizers/api/pre-tokenizers)).
- `models` chứa tất cả các kiểu `Model` bạn có thể sử dụng, như `BPE`, `WordPiece`, and `Unigram` (hoàn thiện danh sách tại [đây](https://huggingface.co/docs/tokenizers/api/models)).
- `trainers` chứa tất cả các kiểu `Trainer` khác nhau bạn có thể sử dụng để huấn luyện mô hình của bạn trên kho ngữ liệu (một cho mỗi loại mô hình; hoàn thiện danh sách tại [đây](https://huggingface.co/docs/tokenizers/api/trainers)).
- `post_processors` chứa tất cả các kiểu `PostProcessor` bạn có thể sử dụng (hoàn thiện danh sách tại [đây](https://huggingface.co/docs/tokenizers/api/post-processors)).
- `decoders` chứa tất cả các kiểu `Decoder` đa dạng bạn có thể sử dụng để giải mã đầu ra của tokenize (hoàn thiện danh sách tại [đây](https://huggingface.co/docs/tokenizers/components#decoders)).

Bạn có thể tìm được toàn bộ danh sách các khối tại [đây](https://huggingface.co/docs/tokenizers/components).

## Thu thập một kho ngữ liệu

Để huấn luyện tokenizer mới của mình, chúng ta sẽ sử dụng một kho ngữ liệu nhỏ chứa các đoạn văn (cho nhanh). Các bước để có được kho ngữ liệu tương tự như chúng ta đã làm ở [phần đầu của chương này](/course/chapter6/2), nhưng lần này chúng ta sẽ sử dụng [WikiText-2](https://huggingface.co/datasets/wikitext):

```python
from datasets import load_dataset

dataset = load_dataset("wikitext", name="wikitext-2-raw-v1", split="train")


def get_training_corpus():
    for i in range(0, len(dataset), 1000):
        yield dataset[i : i + 1000]["text"]
```

Hàm `get_training_corpus()` là một hàm tạo có thể trả về các lô với mỗi lô là 1,000 đoạn văn, cái mà ta sẽ sử dụng để huấn luyện  tokenizer.

🤗 Tokenizers cũng có thể được huấn luyện trực tiếp trên các tệp văn bản. Đây là cách chúng ta tạo ra một tệp văn bản bao gồm các đoạn văn/đầu vào từ WikiText-2 mà ta có thể sử dụng cục bộ:

```python
with open("wikitext-2.txt", "w", encoding="utf-8") as f:
    for i in range(len(dataset)):
        f.write(dataset[i]["text"] + "\n")
```

Tiếp theo chúng tôi sẽ hướng dẫn bạn cách tự xây dựng từng khối BERT, GPT-2, và XLNet tokenizer của riêng mình. Điều này sẽ cung cấp cho chúng ta một ví dụ về từng thuật toán trong số ba thuật toán tokenize chính: WordPiece, BPE, và Unigram. Hãy cũng bắt đầu với BERT!

## Xây dựng một WordPiece tokenizer từ đầu

Để xây dựng một tokenizer với thư viện 🤗 Tokenizers, chúng ta sẽ bắt đầu với việc khởi tạo một đối tượng `Tokenizer` với `model`, sau đó thiết lập `normalizer`, `pre_tokenizer`, `post_processor`, và `decoder` tới các giá trị ta muốn.

Với ví dụ này, ta sẽ tạo ra một `Tokenizer` với một mô hình WordPiece:

```python
from tokenizers import (
    decoders,
    models,
    normalizers,
    pre_tokenizers,
    processors,
    trainers,
    Tokenizer,
)

tokenizer = Tokenizer(models.WordPiece(unk_token="[UNK]"))
```

Chúng ta phải chỉ rõ `unk_token` để mô hình biết phải trả về gì khi gặp các kí tự chưa từng gặp trước đó. Các tham số khác chúng ta có thể cài đặt gồm `vocab` của mô hình (ta sẽ huấn luyện mô hình nên không cần thiết lập nó) và `max_input_chars_per_word`, tương ứng độ dài tối đa cho một từ (từ dài hơn giá trị này sẽ bị chia nhỏ)

Bước đầu tiên để tokenize đó là chuẩn hoá, vì vậy hãy cũng bắt đầu với bước này. Vì BERT được sử dụng rộng tãi, ta có thể sử dụng `BertNormalizer` với tuỳ chọn kinh điển để thiết lập cho BERT: `lowercase` và `strip_accents`, tự cái tên đã giải thích mục đích của chúng; `clean_text` để loại bỏ tất cả các kí tự kiểm soát và dấu cách lặp lại thành một; và `handle_chinese_chars` thêm dấu cách giữa các kí tự tiếng Trung. Để , which places spaces around Chinese characters. To tái tạo tokenizer `bert-base-uncased`, ta có thể thiết lập chuẩn hoá sau:

```python
tokenizer.normalizer = normalizers.BertNormalizer(lowercase=True)
```

Thông thường, khi xây dựng một tokenizer, bạn không cần phải truy cập vào một hàm chuẩn hoá thủ công vì nó đã có sẵn trong thư viện 🤗 Tokenizers library -- tuy nhiên, hãy cùng tạo ra chuẩn hoá BERT thủ công. Thư viện cung câp trình chuẩn hoá `Lowercase` và `StripAccents`, bạn hoàn toàn có thể kết hợp nhiều trình chuẩn hoá với nhau thông qua `Sequence`:

```python
tokenizer.normalizer = normalizers.Sequence(
    [normalizers.NFD(), normalizers.Lowercase(), normalizers.StripAccents()]
)
```

Ta cũng có thể sử dụng chuẩn hoá Unicode `NFD` Unicode normalizer, vì nếu không chuẩn hoá `StripAccents` sẽ không nhận diện được những kí tự có dấu và không thể tách nó đúng như ta muốn.

Như đã thấy ở trên, ta có thể sử dụng phương thức `normalize_str()` của `normalizer` để kiểm tra tác động của nó lên một chuỗi văn bản:

```python
print(tokenizer.normalizer.normalize_str("Héllò hôw are ü?"))
```

```python out
hello how are u?
```

<Tip>

**Đào sâu hơn** Nếu bạn muốn kiểm tra xem hai phiên bản chuẩn hoá trước đó trên cũng một chuỗi chứa kí tự unicode `u"\u0085"`, bạn chắc chắn sẽ nhận thấy rằng hai cách chuẩn hoá này không hoàn toàn giống nhau.
Để tránh phức tạp hoá phiên bản với `normalizers.Sequence` quá nhiều, chúng tôi sẽ không bao gồm các sự thay thế theo Regex mà `BertNormalizer` yêu cầu khi tham số `clean_text` được thiết lập là `True` - đây cũng là giá trị mặc định. Nhưng đừng lo: có khả năng ta sẽ nhận được kết quả chuẩn hoá giống nhau mà không cần sử dụng `BertNormalizer` thủ công bằng cách thêm hai `normalizers.Replace` vào chuỗi chuẩn hoá.

</Tip>

Tiếp theo là bước pre-tokenization. Một lần nữa, ta có `BertPreTokenizer` được xây dựng sẵn để dùng:

```python
tokenizer.pre_tokenizer = pre_tokenizers.BertPreTokenizer()
```

Hoặc ta có thể xây từ đầu:

```python
tokenizer.pre_tokenizer = pre_tokenizers.Whitespace()
```

Lưu ý rằng `Whitespace` sẽ tách theo dấu cách và các kí tự không phải chữ cái, số, hoặc dấu gạch dưới, nên về mặt kỹ thuật nó sẽ tách theo dấu cách và dấu câu:

```python
tokenizer.pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.")
```

```python out
[('Let', (0, 3)), ("'", (3, 4)), ('s', (4, 5)), ('test', (6, 10)), ('my', (11, 13)), ('pre', (14, 17)),
 ('-', (17, 18)), ('tokenizer', (18, 27)), ('.', (27, 28))]
```

Nêu sbanj chỉ muốn tách theo dấu cách, bạn có thể sử dụng `WhitespaceSplit` thay thế:

```python
pre_tokenizer = pre_tokenizers.WhitespaceSplit()
pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.")
```

```python out
[("Let's", (0, 5)), ('test', (6, 10)), ('my', (11, 13)), ('pre-tokenizer.', (14, 28))]
```

Giống như chuẩn hoá, bản có thể sử dụng `Sequence` để kết hợp các tiền tokenizer với nhau:

```python
pre_tokenizer = pre_tokenizers.Sequence(
    [pre_tokenizers.WhitespaceSplit(), pre_tokenizers.Punctuation()]
)
pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.")
```

```python out
[('Let', (0, 3)), ("'", (3, 4)), ('s', (4, 5)), ('test', (6, 10)), ('my', (11, 13)), ('pre', (14, 17)),
 ('-', (17, 18)), ('tokenizer', (18, 27)), ('.', (27, 28))]
```

Bước tiếp theo trong pipeline tokenize là đưa đầu vào qua mô hình. Ta đã chỉ định mô hình của mình khi khởi tạo, nhưng ta vẫn cần huấn luyện nó, điều này cần tới `WordPieceTrainer`.  Vấn đề chính ở đây là khi khởi tạo một trình huấn luyện trong 🤗 Tokenizers thì bạn cần phải truyền tất cả các token đặc biệt bạn có ý định sử dụng, nếu không nó sẽ không thêm vào bộ từ vựng, vì chúng không có trong kho ngữ liệu huấn luyện:

```python
special_tokens = ["[UNK]", "[PAD]", "[CLS]", "[SEP]", "[MASK]"]
trainer = trainers.WordPieceTrainer(vocab_size=25000, special_tokens=special_tokens)
```

Cũng như việc chỉ định `vocab_size` và `special_tokens`, ta cần thiết lập `min_frequency` (số lần một token phải xuất hiện để được thêm vào bộ từ vựng) hoặc thay đổi `continuing_subword_prefix` (nếu ta muốn sử dụng thứ gì khác ngoài `##`).

Để huấn luyện một mô hình sử dụng trình lặp ta định nghĩa trước đó, ta chỉ cần thực hiện lệnh này:

```python
tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer)
```

Chúng ta cũng có thể sử dụng các tệp văn bản để huấn luyện tokenizer của mình như sau (ta tái khởi tạo mô hình với một `WordPiece` rỗng):

```python
tokenizer.model = models.WordPiece(unk_token="[UNK]")
tokenizer.train(["wikitext-2.txt"], trainer=trainer)
```

Trong cả hai trường hợp, ta có thể kiểm tra xem tokenizer trên một đoạn văn bản bằng cách sử dụng phương thức `encode()`:

```python
encoding = tokenizer.encode("Let's test this tokenizer.")
print(encoding.tokens)
```

```python out
['let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.']
```

`encoding` thu được là một `Encoding` gồm tất cả các đầu ra cần thiết của một tokenizer trong tất cả các thông số đa dạng của nó: `ids`, `type_ids`, `tokens`, `offsets`, `attention_mask`, `special_tokens_mask`, và `overflowing`.

Bước cuối của quy trình đó là hậu xử lý. Ta cần thêm token `[CLS]` token at the beginning and the `[SEP]` ở cuối (hoặc sau mỗi câu, nếu ta có cặp câu). Chúng ta sẽ sử dụng `TemplateProcessor` để thực hiện điều này, nhưng trước hết ta cần biết các ID của token `[CLS]` và `[SEP]` trong bộ từ vựng.

```python
cls_token_id = tokenizer.token_to_id("[CLS]")
sep_token_id = tokenizer.token_to_id("[SEP]")
print(cls_token_id, sep_token_id)
```

```python out
(2, 3)
```

Để viết bản mẫu cho `TemplateProcessor`, chúng ta phải chỉ định cách xử lý một câu đơn và một cặp câu. Đối với cả hai, chúng tôi viết các token đặc biệt muốn sử dụng; câu đầu tiên (hoặc câu đơn) được biểu thị bằng `$A`, trong khi câu thứ hai (nếu token một cặp) được biểu thị bằng `$B`. Đối với mỗi loại trong số này (token và câu đặc biệt), chúng ta cũng chỉ định loại token ID tương ứng sau dấu hai chấm.

Do đó, bản mẫu BERT cổ điển được định nghĩa như sau:

```python
tokenizer.post_processor = processors.TemplateProcessing(
    single=f"[CLS]:0 $A:0 [SEP]:0",
    pair=f"[CLS]:0 $A:0 [SEP]:0 $B:1 [SEP]:1",
    special_tokens=[("[CLS]", cls_token_id), ("[SEP]", sep_token_id)],
)
```

Lưu ý rằng chúng ta cần truyền vào tất cả các IDs của các kí tự đặc biệt, nên các tokenize có thể chuyển đổi chúng thành các cặp ID.

Một khi đã được thêm vào, chúng ta có thể quay lại ví dụ trước đó và sẽ nhận được:

```python
encoding = tokenizer.encode("Let's test this tokenizer.")
print(encoding.tokens)
```

```python out
['[CLS]', 'let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.', '[SEP]']
```

Và trên một cặp câu, chúng ta có thể có được kết quả sau:

```python
encoding = tokenizer.encode("Let's test this tokenizer...", "on a pair of sentences.")
print(encoding.tokens)
print(encoding.type_ids)
```

```python out
['[CLS]', 'let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '...', '[SEP]', 'on', 'a', 'pair', 'of', 'sentences', '.', '[SEP]']
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1]
```

Chúng ta đã gần như hoàn thành việc xây dựng tokenizer này từ đầu -- bước cuối cùng là thêm vào một trình giải mã:

```python
tokenizer.decoder = decoders.WordPiece(prefix="##")
```

Hãy cũng kiểm thử với `encoding`:

```python
tokenizer.decode(encoding.ids)
```

```python out
"let's test this tokenizer... on a pair of sentences."
```

Tuyệt vời! Ta có thể lưu tokenizer của mình vào trong một tệp JSON như dưới đây:

```python
tokenizer.save("tokenizer.json")
```

Ta sau đó có thể tải lại tệp này trong đối tượng `Tokenizer` với phương thức `from_file()`:

```python
new_tokenizer = Tokenizer.from_file("tokenizer.json")
```

Để sử dụng tokenizer này trong 🤗 Transformers, chúng ta phải bọc nó trong `PreTrainedTokenizerFast`. Chúng ta có thể sử dụng lớp chung hoặc, nếu tokenizer của chúng ta tương ứng với một mô hình hiện có, hãy sử dụng lớp đó (ở đây là `BertTokenizerFast`). Nếu bạn áp dụng bài học này để xây dựng một tokenizer hoàn toàn mới, bạn sẽ phải sử dụng tùy chọn đầu tiên.

Để bọc tokenizer trong một `PreTrainedTokenizerFast`, chúng ta có thể chuyển tokenizer mà chúng ta đã xây dựng dưới dạng `tokenizer_object` hoặc truyền tệp tokenizer mà chúng ta đã lưu dưới dạng `tokenizer_file`. Điều quan trọng cần nhớ là chúng ta phải đặt thủ công tất cả các token đặc biệt, vì lớp đó không thể suy ra từ đối tượng `tokenizer` token nào là token bị che, `[CLS]`, v.v.:

```python
from transformers import PreTrainedTokenizerFast

wrapped_tokenizer = PreTrainedTokenizerFast(
    tokenizer_object=tokenizer,
    # tokenizer_file="tokenizer.json", # Bạn có thể tải từ tệp tokenizer
    unk_token="[UNK]",
    pad_token="[PAD]",
    cls_token="[CLS]",
    sep_token="[SEP]",
    mask_token="[MASK]",
)
```

Nếu bạn đang sự dụng một lớp tokenizer đặc biệt (như `BertTokenizerFast`), bạn chỉ cần chỉ định một token đặc biết khác so với mặc định (ở đây là không xác định):

```python
from transformers import BertTokenizerFast

wrapped_tokenizer = BertTokenizerFast(tokenizer_object=tokenizer)
```

Bạn có thể sử dụng tokenizer như bất kỳ tokenizer nào khác của 🤗 Transformers. Bạn có thể lưu nó với phương thức `save_pretrained()`, hoặc lại nó lên Hub sử dụng phương thức `push_to_hub()`.

Giờ chúng ta đã thấy cách xây dựng bộ WordPiece tokenizer, hãy làm tương tự đối với BPE tokenizer. Chúng ta sẽ tiến hành nhanh hơn một chút vì bạn đã biết tất cả các bước và chỉ làm nổi bật những điểm khác biệt.

## Xây dựng một BPE tokenizer từ đầu

Giờ hãy cũng nhau xây dựng GPT-2 tokenizer. Giống như BERT tokenizer, chúng ta bắt đầu bằng việc khởi tạo `Tokenizer` với mô hình BPE:

```python
tokenizer = Tokenizer(models.BPE())
```

Cũng giống như BERT, chúng ta có thể khởi tạo mô hình này với một bộ từ vựng nếu ta đã có (ta sẽ cần truyền vào `vocab` và `merges` trong trường hợp này), nhưng vì ta sẽ huấn luyện từ đầu, chúng ta không cần làm vậy. Ta cũng không cần chỉ định `unk_token` vì GPT-2 sử dụng BPE cấp byte, phương pháp không cần đến nó.

GPT-2 không sử dụng một trình chuẩn hoá, nên ta có thể bỏ qua bước này và đi trực tiếp vào bước pre-tokenization:

```python
tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False)
```

Tuỳ chọn `ByteLevel` chúng ta thêm vào ở đây không thêm dấu cách vào đầu của một câu (thường nó là mặc định). Ta có thể nhìn các pre-tokenization từ ví dụ tương tự ở trên:

```python
tokenizer.pre_tokenizer.pre_tokenize_str("Let's test pre-tokenization!")
```

```python out
[('Let', (0, 3)), ("'s", (3, 5)), ('Ġtest', (5, 10)), ('Ġpre', (10, 14)), ('-', (14, 15)),
 ('tokenization', (15, 27)), ('!', (27, 28))]
```

Tiếp theo là mô hình mà ta cần huấn luyện. Với GPT-2, token đặc biệt duy nhất ta cần là token kết thúc văn bản: 

```python
trainer = trainers.BpeTrainer(vocab_size=25000, special_tokens=["<|endoftext|>"])
tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer)
```

Như với `WordPieceTrainer`, cũng như `vocab_size` và `special_tokens`, ta có thể chỉ định `min_frequency` nếu muốn, hoặc nếu ta có hậu tố kết thúc từ (như `</w>`), ta có thể thiết lập nó với `end_of_word_suffix`.

Tokenizer này cũng có thể được huấn luyện trên các tệp văn bản:

```python
tokenizer.model = models.BPE()
tokenizer.train(["wikitext-2.txt"], trainer=trainer)
```

Hãy cũng xem kết quả tokenize trên một văn bản mẫu:

```python
encoding = tokenizer.encode("Let's test this tokenizer.")
print(encoding.tokens)
```

```python out
['L', 'et', "'", 's', 'Ġtest', 'Ġthis', 'Ġto', 'ken', 'izer', '.']
```

Ta áp dụng hậu xử lý cấp byte cho GPT-2 tokenizer như sau:

```python
tokenizer.post_processor = processors.ByteLevel(trim_offsets=False)
```

Tuỳ chọn `trim_offsets = False` chỉ cho trình hậu xử lý biết rằng ta cần bỏ mốt số offset token bắt đầu với 'Ġ': theo cách này, điểm bắt đầu của offset sẽ trỏ vào vùng không gian phía trước của từ, không phải kí tự đầu tiên của từ (vì không gian này về mặt kỹ thuật là một phần của từ). Hãy cùng nhìn xem kết quả với chuỗi văn bản ta vừa mã hoá với `'Ġtest'` là token ở chỉ mục 4:

```python
sentence = "Let's test this tokenizer."
encoding = tokenizer.encode(sentence)
start, end = encoding.offsets[4]
sentence[start:end]
```

```python out
' test'
```

Cuối cùng, ta thêm một trình giải mãi cấp byte:

```python
tokenizer.decoder = decoders.ByteLevel()
```

và ta kiểm tra lại xem nó hoạt động đúng chưa:

```python
tokenizer.decode(encoding.ids)
```

```python out
"Let's test this tokenizer."
```

Tuyệt vời! Giờ ta đã xong rồi, ta có thể lưu tokenizer như trên, và bao nó lại trong `PreTrainedTokenizerFast` hoặc  `GPT2TokenizerFast` nếu ta muốn nó trong 🤗 Transformers:

```python
from transformers import PreTrainedTokenizerFast

wrapped_tokenizer = PreTrainedTokenizerFast(
    tokenizer_object=tokenizer,
    bos_token="<|endoftext|>",
    eos_token="<|endoftext|>",
)
```

or:

```python
from transformers import GPT2TokenizerFast

wrapped_tokenizer = GPT2TokenizerFast(tokenizer_object=tokenizer)
```

Như một ví dụ cuối, chúng tôi sẽ chỉ bạn cách xây dựng một Unigram tokenizer từ đầu.

## Xây dựng một Unigram tokenizer từ đầu

Hãy cùng nhau xây dựng một XLNet tokenizer. Cũng giống như các tokenizer trước đó, ta có thể bắt đầu khởi tạo `Tokenizer` với một mô hình Unigram:

```python
tokenizer = Tokenizer(models.Unigram())
```

Một lần nữa, chúng ta có thể khởi tạo mô hình này với một từ vựng nếu có.

Với sự chuẩn hoá này, XLNet sử dụng một vài phương pháp thay thế (đến từ SentencePiece):

```python
from tokenizers import Regex

tokenizer.normalizer = normalizers.Sequence(
    [
        normalizers.Replace("``", '"'),
        normalizers.Replace("''", '"'),
        normalizers.NFKD(),
        normalizers.StripAccents(),
        normalizers.Replace(Regex(" {2,}"), " "),
    ]
)
```

Điều này thay thế <code>``</code> and <code>''</code> bằng <code>"</code> và thay thế bất kì chuỗi nào chứa hai hoặc nhiều hơn dấu cách liền nhau thành một dấu duy nhất, cũng như loại bỏ các dấu có trong văn bản để tokenize.

Tiền tokenizer được sử dụng cho bất kỳ SentencePiece tokenizer là `Metaspace`:

```python
tokenizer.pre_tokenizer = pre_tokenizers.Metaspace()
```

Ta có thể nhìn vào đầu ra quy trình tiền tokenize qua ví dụ văn bản ở dưới:

```python
tokenizer.pre_tokenizer.pre_tokenize_str("Let's test the pre-tokenizer!")
```

```python out
[("▁Let's", (0, 5)), ('▁test', (5, 10)), ('▁the', (10, 14)), ('▁pre-tokenizer!', (14, 29))]
```

Tiếp theo là mô hình ta cần huấn luyện. XLNet có một số token đặc biệt:

```python
special_tokens = ["<cls>", "<sep>", "<unk>", "<pad>", "<mask>", "<s>", "</s>"]
trainer = trainers.UnigramTrainer(
    vocab_size=25000, special_tokens=special_tokens, unk_token="<unk>"
)
tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer)
```

Một tham số vô cùng quan trong mà ta không thể quên của `UnigramTrainer` là `unk_token`. Ta có thể truyền vào các tham số cụ thể khác tới thuật toán Unigram, ví dụ `shrinking_factor` cho các bước mà ta xoá token (mặc định là 0.75) hoặc `max_piece_length` để chỉ định độ dài tối đa của một token (mặc định là 16).

Tokenizer này có thể được huấn luyện trên các tệp văn bản:

```python
tokenizer.model = models.Unigram()
tokenizer.train(["wikitext-2.txt"], trainer=trainer)
```

Hãy cùng nhìn xem kết quả tokenize trên tập mẫu:

```python
encoding = tokenizer.encode("Let's test this tokenizer.")
print(encoding.tokens)
```

```python out
['▁Let', "'", 's', '▁test', '▁this', '▁to', 'ken', 'izer', '.']
```

Một điểm đặc biệt của XLNet đó là nó thêm token `<cls>` ở cuối mỗi câu, với kiểu ID là 2 (để phân biết với các token khác). Nó đệm thêm vào phía bên tay trái giống như kết quả ở trên. Ta có thể xử lý tất cả các token đặc biệt và các token kiểu ID với cùng một bản mẫu, như BERT, nhưng đầu tiên ta phải lấy các ID của token `<cls>` và `<sep>`:

```python
cls_token_id = tokenizer.token_to_id("<cls>")
sep_token_id = tokenizer.token_to_id("<sep>")
print(cls_token_id, sep_token_id)
```

```python out
0 1
```

Bản mẫu sẽ trông như sau:

```python
tokenizer.post_processor = processors.TemplateProcessing(
    single="$A:0 <sep>:0 <cls>:2",
    pair="$A:0 <sep>:0 $B:1 <sep>:1 <cls>:2",
    special_tokens=[("<sep>", sep_token_id), ("<cls>", cls_token_id)],
)
```

Và ta có thể kiểm tra xem nó hoạt động không bằng cách mã hoá cặp câu:

```python
encoding = tokenizer.encode("Let's test this tokenizer...", "on a pair of sentences!")
print(encoding.tokens)
print(encoding.type_ids)
```

```python out
['▁Let', "'", 's', '▁test', '▁this', '▁to', 'ken', 'izer', '.', '.', '.', '<sep>', '▁', 'on', '▁', 'a', '▁pair', 
  '▁of', '▁sentence', 's', '!', '<sep>', '<cls>']
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2]
```

Cuối cùng, ta sẽ thêm trình giải mã `Metaspace`:

```python
tokenizer.decoder = decoders.Metaspace()
```

và ta đã xong với tokenizer này! Ta có thể lưu tokenizer như trên, và bao nó lại trong `PreTrainedTokenizerFast` hoặc  `XLNetTokenizerFast` nếu ta muốn nó trong 🤗 Transformers. Một điểm cần lưu ý là khi sử dụng `PreTrainedTokenizerFast` thì trên đầu của các token đặc biệt ta cần nói cho thư viện 🤗 Transformers viết ta cần đệm vào phía bên trái:

```python
from transformers import PreTrainedTokenizerFast

wrapped_tokenizer = PreTrainedTokenizerFast(
    tokenizer_object=tokenizer,
    bos_token="<s>",
    eos_token="</s>",
    unk_token="<unk>",
    pad_token="<pad>",
    cls_token="<cls>",
    sep_token="<sep>",
    mask_token="<mask>",
    padding_side="left",
)
```

Hoặc một cách khác:

```python
from transformers import XLNetTokenizerFast

wrapped_tokenizer = XLNetTokenizerFast(tokenizer_object=tokenizer)
```

Bây giờ bạn đã thấy cách các khối khác nhau được sử dụng để xây dựng các tokenizer hiện nay, bạn sẽ có thể viết bất kỳ trình tokenize nào mà bạn muốn với thư viện 🤗 Tokenizers và có thể sử dụng nó trong 🤗 Transformers.
